
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.util {
import mx.collections.IViewCursor;

import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationMap;
import org.waveprotocol.wave.model.util.Preconditions;


public class ImmutableAttributesStateMap {
	
  private var attriMap:HashMap;

  private var attributes:ArrayList;

//  private final Set entrySet = new AbstractSet() {
//
//        
//        public Iterator iterator() {
//          return new Iterator() {
//            private Iterator iterator = attributes.iterator();
//            public boolean hasNext() {
//              return iterator.hasNext();
//            }
//            public Attribute next() {
//              Object obj = iterator.next();
//              return (Attribute) obj;
//            }
//            public void remove() {
//              throw new UnsupportedOperationException("Attempt to modify an immutable set.");
//            }
//          };
//        }
//
//        
//        public int size() {
//          return attributes.size();
//        }
//
//      };

	public function ImmutableAttributesStateMap(attributes:ArrayList) {
		    if (attributes == null) {
			    this.attributes = new ArrayList();
			    this.attriMap = new HashMap();
		    } else { 
		    	this.attributes = attributes;
		    	this.attriMap = getMap(attributes);
		    }
	  }
  
	private static function getMap(attributes2:ArrayList):HashMap {
		var size:int= attributes2.length;
		var attributeMap:HashMap= new HashMap();
		for (var i:int=0; i< size; i++ ) {
	         try {
	        	 var temp:Attribute= Attribute(attributes2.getItemAt(i));
	        	 attributeMap.addItem(temp.name, temp.value);  	
	         } catch(e:Error) {
	         }
	    }
	    return attributeMap;
	}
//  /**
//   * Creates a new T object containing no T.
//   */
//  public ImmutableStateMap() {
//    attributes = new ArrayList();
//  }
  
//
//  private static final Comparator comparator = new Comparator() {
//    
//    public int compare(Attribute a, Attribute b) {
//      return a.name.compareTo(b.name);
//    }
//  };

//  /**
//   * Constructs a new <code>T</code> object with the T
//   * specified by the given mapping.
//   *
//   * @param map The mapping of attribute names to attribute values.
//   */
//  public ImmutableStateMap(Map map) {
//    this.attributes = attributeListFromMap(map);
//  }

//  public ImmutableStateMap(String ... pairs) {
//    if (pairs.length % 2 != 0) {
//      throw new Error("Pairs must contain an even number of strings");
//    }
//
//    Map<String, String> map = new HashMap<String, String>();
//
//    for (int i = 0; i < pairs.length; i += 2) {
//      Preconditions.checkNotNull(pairs[i], "Null key");
//      Preconditions.checkNotNull(pairs[i + 1], "Null value");
//      if (map.containsKey(pairs[i])) {
//        throw new Error("Duplicate key: " + pairs[i]);
//      }
//      map.put(pairs[i], pairs[i + 1]);
//    }
//
//    this.attributes = attributeListFromMap(map);
//  }

  public static function attributeListFromMap(map:HashMap):ArrayList {
    var attributeList:ArrayList= new ArrayList();
	var keys:Array = map.keys;
	for (var i:int=0; i<keys.length; i++) {
        var key:String = keys[i];
        if (key == null || map.getItem(key) == null) {
            throw new Error("Null POinter: This map does not allow null keys or values");
          }
          attributeList.addItem(new Attribute(key, String(map.getItem(key))));    	
    }
    //FIXME
//    Collections.sort(attributeList, comparator);
    return attributeList;
  }

  // Public method that is overridden by specifying a different # of arguments  
  public function updateWith(attributeUpdate:AttributesUpdate, checkCompatibility:Boolean=false):Attributes {  
     // figure out how many parameters we were passed in  
     // by arguments.length, and call the appropriate  
     // helper by the function.apply()... the help panel has  
 	// good documentation about this.  
    return  this["updateWith"+arguments.length].apply(this, arguments);  
  }  
  
  /**
   * Returns a <code>T</code> object obtained by applying the update
   * specified by the <code>Object</code> object into this
   * <code>T</code> object.
   *
   * @param attributeUpdate The update to apply.
   * @return A <code>T</code> object obtained by applying the given
   *         update onto this object.
   */
  public function updateWith1(attributeUpdate:AttributesUpdate):StateMap {
    return updateWith2(attributeUpdate, true);
  }

  public function updateWithNoCompatibilityCheck(attributeUpdate:AttributesUpdate):Attributes {
    return updateWith2(attributeUpdate, false);
  }

  private function updateWith2(attributeUpdate:UpdateMap, checkCompatibility:Boolean):Attributes {
	  var newImmutableStateMap:ArrayList= new ArrayList();
    var iterator:IViewCursor= attributes.createCursor();
    var nextAttribute:Attribute= Attribute(iterator.current);
    // TODO: Have a slow path when the cast would fail.
//    List<AttributeUpdate> updates = (ImmutableUpdateMap(attributeUpdate)).updates;
	var temList:ArrayList = (ImmutableAttributesUpdateMap(attributeUpdate)).updates;
	var size1:int = temList.length;
	for (var i1:int=0; i1< size1; i1++ ) {			
		var update:AttributeUpdate = temList.getItemAt(i1) as AttributeUpdate;
      while (nextAttribute != null) {
        var comparison:int= update.name.localeCompare(nextAttribute.name);
        if (comparison > 0) {
          newImmutableStateMap.addItem(nextAttribute);
          nextAttribute = Attribute(iterator.current);
		  iterator.moveNext();
        } else if (comparison < 0) {
          if (checkCompatibility && update.oldValue != null) {
            throw new Error(
                "Mismatched old value: attempt to update unset attribute with " + update);
          }
          break;
        } else if (comparison == 0) {
          if (checkCompatibility && !nextAttribute.value == update.oldValue) {
            throw new Error("Mismatched old value: attempt to update " +
                nextAttribute + " with " + update);
          }
          nextAttribute = Attribute(iterator.current);
		  iterator.moveNext();
          break;
        }
      }
      if (update.newValue != null) {
        newImmutableStateMap.addItem(new Attribute(update.name, update.newValue));
      }
    }
    if (nextAttribute != null) {
      newImmutableStateMap.addItem(nextAttribute);
      while (!iterator.afterLast) {
        newImmutableStateMap.addItem(iterator.current);
		iterator.moveNext();
      }
    }
    return createFromList(newImmutableStateMap);
  }

  protected function createFromList(attributes:ArrayList):Attributes{return null} ;

  public static function checkAttributesSorted(attributes:ArrayList):void {
    var previous:Attribute= null;
//    ArrayList<Attribute> attr = attributes;
    for (var a1:Object in attributes) {
		var a:Attribute = Attribute(a1);
      Preconditions.checkNotNull(a, "Null attribute");
      org.waveprotocol.wave.model.util.Assert.assert(a.name != null);
      org.waveprotocol.wave.model.util.Assert.assert(a.value != null);
      if (previous != null) {
        Preconditions.checkArgument(previous.name.localeCompare(a.name) < 0,
            "Attribute keys not strictly monotonic: " + previous.name + ", " + a.name);
      }
      previous = a;
    }
  }

  public static function updateWithoutCompatibilityCheck(state:Object, update:AttributesUpdate):Attributes {
    // the cast below is required to work with javac from OpenJDK 7
    return (ImmutableAttributesStateMap(state)).updateWith(update, false);
  }
  
  
    public function getValue(key:String):String {
		return String(attriMap.getItem(key));
	}

	public function keySet():Array {
		return attriMap.keys;
	}

	public function isEmpty():Boolean {
		return attriMap.keys.length == 0;
	}

	public function containsKey(key:String):Boolean {
		return attriMap.containsKey(key);
	}
	
	public function size():int {
		return attriMap.keys.length;
	}
}
}